<?php

/**
 * 日志记录
 * Class Logger
 */
class Logger
{
    /**
     * 单例对象
     * @var Logger
     */
    protected static $_instance = null;

    /**
     * 保存日志的基路径
     * @var string
     */
    protected static $basePath = '/www/wwwlogs';

    /**
     * 默认日志的模块
     * @var string
     */
    protected static $module = 'default';

    /**
     * 私有化构造函数
     * Logger constructor.
     */
    protected function __construct()
    {
        // 注册错误处理
        set_error_handler(array($this, 'handleError'));

        // 注册致命错误时的处理
        register_shutdown_function(array($this, 'handleFatalPHPError'));
    }

    /**
     * 处理致命错误
     */
    public function handleFatalPHPError()
    {
        $last_error = error_get_last();
        if ($last_error) {
            $this->handleError($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
        }
    }

    /**
     * 自定义的错误处理函数
     * @param int $errno        包含了错误的级别，是一个 integer
     * @param string $errstr    包含了错误的信息，是一个 string
     * @param string $errfile   可选的，包含了发生错误的文件名，是一个 string
     * @param int $errline      可选项，包含了错误发生的行号，是一个 integer
     * @return bool
     * @link https://www.php.net/manual/zh/function.set-error-handler.php
     */
    public function handleError($errno, $errstr, $errfile = '', $errline = 0)
    {
        $error = 'default';
        switch ($errno) {
            case E_PARSE:
            case E_ERROR:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
                $error = 'error';
                break;
            case E_WARNING:
            case E_USER_WARNING:
            case E_COMPILE_WARNING:
            case E_RECOVERABLE_ERROR:
                $error = 'warning';
                break;
            case E_NOTICE:
            case E_USER_NOTICE:
                $error = 'notice';
                break;
            case E_STRICT:
                $error = 'strict';
                break;
            case E_DEPRECATED:
            case E_USER_DEPRECATED:
                $error = 'deprecated';
                break;
            default:
                break;
        }

        $context = array(
            'error' => $error,
            'errno' => $errno,
            'errstr' => $errstr,
            'errfile' => $errfile,
            'errline' => $errline,
            'time' => time(),
        );

        $this->reportError($context);

        return true;
    }

    /**
     * 上报错误
     * @param array $context
     */
    protected function reportError($context)
    {
        $message = static::T('{error} ({errno}): {errstr} in [File: {errfile}, Line: {errline}, Time: {time}]', $context);
        static::log($context['error'], $message);
    }

    /**
     * 动态翻译替换
     * @param string $str      翻译的内容
     * @param array $params     动态参数
     * @return string
     */
    protected static function T(string $str, $params = array()):string
    {
        if (empty($params) || !is_array($params)) {
            return $str;
        }
        return str_replace(static::formatKeys($params), array_values($params), $str);
    }

    /**
     * 格式化键名
     * @param array $params
     * @return array
     */
    protected static function formatKeys(array $params):array
    {
        return array_map(function ($k) {
            return '{' . $k . '}';
        }, array_keys($params));
    }

    /**
     * 根据不同错误，保存到相应的日志文件
     * @param string|int $type
     * @param string|int $module
     * @return string
     */
    protected static function getLogger($type, string $module):string
    {
        clearstatcache();
        if (is_dir(self::$basePath) && is_writeable(self::$basePath)) {
            $module = empty($module) ? self::$module : $module;
            $dir = self::$basePath  . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . date('Ym');
            // 递归创建目录
            is_dir($dir) or mkdir($dir, 0755, true);
            $logger = $dir . DIRECTORY_SEPARATOR . $type . date('_Ymd') . '.log';
            if (!file_exists($logger)) {
                touch($logger);
                chmod($logger, 0755);
            }
            return $logger;
        } else {
            die(self::$basePath . ' 无权限');
        }
    }

    /**
     * 单例
     * @return Logger
     */
    protected static function one():Logger
    {
        if (self::$_instance === null) {
            self::$_instance = new static();
        }
        return self::$_instance;
    }

    /**
     * 设置basePath
     *
     * @param $basePath
     */
    public static function setBasePath(string $basePath)
    {
        static::one();
        self::$basePath = $basePath;
    }

    /**
     * 设置模块目录
     *
     * @param $module
     */
    public static function setLogger(string $module = 'default')
    {
        static::one();
        self::$module = $module;
    }

    /**
     * 通用日志方法
     *
     * @param string|int $level
     * @param string|array|object $message
     * @param array $context
     * @param string|int $module
     * @return bool
     */
    public static function log($level, $message, array $context = array(), $module = '')
    {
        // 1. 转字符串
        if (is_array($message) || is_object($message)) {
            $message = print_r($message, true);
        }
        // 2. 替换动态参数
        if (!empty($context)) {
            $message = static::T($message, $context);
        }
        // 3. 获得日志文件
        $logger = static::getLogger(strtolower($level), $module);
        // 4. 格式化样式
        $log = sprintf("%s | %s | %s | %s | \n%s\n", date('Y-m-d H:i:s'), strtoupper($level), uniqid(), microtime(true), $message);
        // 5. 保存
        $writeLen = file_put_contents($logger, $log, FILE_APPEND);

        return (bool)$writeLen;
    }

    /**
     * 记录debug日志
     *
     * @param string|array $message
     * @param array        $context
     * @param string       $module
     * @return bool
     */
    public static function debug($message, array $context = array(), $module = '')
    {
        return static::log('debug', $message, $context, $module);
    }

    /**
     * 记录info日志
     *
     * @param string|array $message
     * @param array        $context
     * @param string       $module
     * @return bool
     */
    public static function info($message, array $context = array(), $module = '')
    {
        return static::log('info', $message, $context, $module);
    }

    /**
     * 记录notice日志
     *
     * @param string|array $message
     * @param array        $context
     * @param string       $module
     * @return bool
     */
    public static function notice($message, array $context = array(), $module = '')
    {
        return static::log('notice', $message, $context, $module);
    }

    /**
     * 记录warning日志
     *
     * @param string|array $message
     * @param array        $context
     * @param string       $module
     * @return bool
     */
    public static function warning($message, array $context = array(), $module = '')
    {
        return static::log('warning', $message, $context, $module);
    }

    /**
     * 记录error日志
     *
     * @param string|array $message
     * @param array        $context
     * @param string       $module
     * @return bool
     */
    public static function error($message, array $context = array(), $module = '')
    {
        return static::log('error', $message, $context, $module);
    }
}
